/*
 * Copyright (C) 2019 ByteDance Inc
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.bytedance.scene.ui;


import com.bytedance.scene.*;
import com.bytedance.scene.navigation.NavigationScene;
import com.bytedance.scene.navigation.NavigationSceneOptions;
import com.bytedance.scene.utlity.SceneInstanceUtility;
import com.bytedance.scene.utlity.ThreadUtility;
import com.bytedance.scene.utlity.Utility;
import ohos.aafwk.ability.Lifecycle;
import ohos.aafwk.ability.LifecycleObserver;
import ohos.aafwk.ability.LifecycleStateObserver;
import ohos.aafwk.ability.fraction.Fraction;
import ohos.aafwk.ability.fraction.FractionManager;
import ohos.aafwk.ability.fraction.FractionScheduler;
import ohos.aafwk.content.Intent;
import ohos.aafwk.content.IntentParams;
import ohos.agp.components.Component;

import java.util.HashSet;
import java.util.Optional;
import java.util.WeakHashMap;

/**
 * Created by JiangQi on 9/4/18.
 */
public final class NavigationSceneCompatUtility {
    static final String LIFE_CYCLE_FRAGMENT_TAG = "LifeCycleCompatFragment";
    private static final WeakHashMap<Fraction, HashSet<String>> CHECK_DUPLICATE_TAG_MAP = new WeakHashMap<>();

    /**
     * 空构造
     */
    private NavigationSceneCompatUtility() {
    }

    /**
     * Builder类
     *
     * @since 2021-04-06
     */
    public static final class Builder {

        private final Fraction mFragment;

        private final Class<? extends Scene> mRootSceneClazz;

        private IntentParams mRootSceneArguments;
        private boolean mDrawWindowBackground = true;
        private boolean mFixSceneBackgroundEnabled = true;

        private int mSceneBackgroundResId = 0;

        private final int mIdRes;
        private boolean mSupportRestore = false;

        private SceneComponentFactory mRootSceneComponentFactory;

        private String mTag = LIFE_CYCLE_FRAGMENT_TAG;
        private boolean mImmediate = true;

        /**
         * 构造
         *
         * @param fragment       fragment
         * @param rootSceneClazz rootSceneClazz
         * @param containerId    containerId
         */
        private Builder(Fraction fragment, Class<? extends Scene> rootSceneClazz, int containerId) {
            this.mFragment = Utility.requireNonNull(fragment, "Fraction can't be null");
            this.mRootSceneClazz = Utility.requireNonNull(rootSceneClazz, "Root Scene class can't be null");
            this.mIdRes = containerId;
        }

        /**
         * rootSceneArguments
         *
         * @param rootSceneArguments
         * @return Builder
         */
        public Builder rootSceneArguments(IntentParams rootSceneArguments) {
            this.mRootSceneArguments = rootSceneArguments;
            return this;
        }

        /**
         * supportRestore
         *
         * @param supportRestore
         * @return Builder
         */
        public Builder supportRestore(boolean supportRestore) {
            this.mSupportRestore = supportRestore;
            return this;
        }

        /**
         * rootSceneComponentFactory
         *
         * @param rootSceneComponentFactory
         * @return Builder
         */
        public Builder rootSceneComponentFactory(SceneComponentFactory rootSceneComponentFactory) {
            this.mRootSceneComponentFactory = rootSceneComponentFactory;
            return this;
        }

        /**
         * drawWindowBackground
         *
         * @param drawWindowBackground
         * @return Builder
         */
        public Builder drawWindowBackground(boolean drawWindowBackground) {
            this.mDrawWindowBackground = drawWindowBackground;
            return this;
        }

        /**
         * fixSceneWindowBackgroundEnabled
         *
         * @param fixSceneBackground
         * @return Builder
         */
        public Builder fixSceneWindowBackgroundEnabled(boolean fixSceneBackground) {
            this.mFixSceneBackgroundEnabled = fixSceneBackground;
            return this;
        }

        /**
         * sceneBackground
         *
         * @param resId
         * @return Builder
         */
        public Builder sceneBackground(int resId) {
            this.mSceneBackgroundResId = resId;
            return this;
        }

        /**
         * tag
         *
         * @param tag
         * @return Builder
         */
        public Builder tag(String tag) {
            this.mTag = Utility.requireNonNull(tag, "Tag can't be null");
            return this;
        }

        /**
         * immediate
         *
         * @param immediate
         * @return Builder
         */
        public Builder immediate(boolean immediate) {
            this.mImmediate = immediate;
            return this;
        }

        /**
         * build
         *
         * @return SceneDelegate
         */
        public SceneDelegate build() {
            NavigationSceneOptions navigationSceneOptions = new NavigationSceneOptions(this.mRootSceneClazz, this.mRootSceneArguments);
            navigationSceneOptions.setDrawWindowBackground(this.mDrawWindowBackground);
            navigationSceneOptions.setFixSceneWindowBackgroundEnabled(this.mFixSceneBackgroundEnabled);
            navigationSceneOptions.setSceneBackground(this.mSceneBackgroundResId);
            return setupWithFragment(this.mFragment, this.mIdRes, navigationSceneOptions, this.mRootSceneComponentFactory, this.mSupportRestore, this.mTag, this.mImmediate);
        }
    }

    /**
     * setupWithFragment
     *
     * @param fragment
     * @param rootScene
     * @param containerId
     * @return Builder
     */
    public static Builder setupWithFragment(final Fraction fragment,
                                            Class<? extends Scene> rootScene,
                                            int containerId) {
        return new Builder(fragment, rootScene, containerId);
    }

    /**
     * setupWithFragment
     *
     * @param fragment
     * @param containerId
     * @param intent
     * @param rootScene
     * @param bundle
     * @return SceneDelegate
     */
    @Deprecated
    public static SceneDelegate setupWithFragment(final Fraction fragment,
                                                  int containerId,
                                                  Intent intent,
                                                  Class<? extends Scene> rootScene,
                                                  IntentParams bundle) {
        return setupWithFragment(fragment, containerId, intent,
                new NavigationSceneOptions(rootScene, bundle),
                null,
                false);
    }

    /**
     * setupWithFragment
     *
     * @param fragment
     * @param containerId
     * @param intent
     * @param rootScene
     * @param bundle
     * @param supportRestore
     * @return SceneDelegate
     */
    @Deprecated
    public static SceneDelegate setupWithFragment(final Fraction fragment,
                                                  int containerId,
                                                  Intent intent,
                                                  Class<? extends Scene> rootScene,
                                                  IntentParams bundle,
                                                  boolean supportRestore) {
        return setupWithFragment(fragment, containerId, intent,
                new NavigationSceneOptions(rootScene, bundle),
                null,
                supportRestore);
    }

    /**
     * setupWithFragment
     *
     * @param fragment
     * @param containerId
     * @param intent
     * @param rootScene
     * @param rootSceneComponentFactory
     * @param bundle
     * @param supportRestore
     * @return SceneDelegate
     */
    @Deprecated
    public static SceneDelegate setupWithFragment(final Fraction fragment,
                                                  int containerId,
                                                  Intent intent,
                                                  Class<? extends Scene> rootScene,
                                                  SceneComponentFactory rootSceneComponentFactory,
                                                  IntentParams bundle,
                                                  boolean supportRestore) {
        return setupWithFragment(fragment, containerId, intent,
                new NavigationSceneOptions(rootScene, bundle),
                rootSceneComponentFactory,
                supportRestore);
    }

    /**
     * setupWithFragment
     *
     * @param fragment
     * @param containerId
     * @param intent
     * @param navigationSceneOptions
     * @param rootSceneComponentFactory
     * @param supportRestore
     * @return SceneDelegate
     */
    @Deprecated
    public static SceneDelegate setupWithFragment(final Fraction fragment,
                                                  int containerId,
                                                  Intent intent,
                                                  NavigationSceneOptions navigationSceneOptions,
                                                  SceneComponentFactory rootSceneComponentFactory,
                                                  final boolean supportRestore) {
        return setupWithFragment(fragment, containerId, intent, navigationSceneOptions,
                rootSceneComponentFactory, supportRestore, LIFE_CYCLE_FRAGMENT_TAG, true);
    }

    /**
     * setupWithFragment
     *
     * @param fragment
     * @param containerId
     * @param intent
     * @param navigationSceneOptions
     * @param rootSceneComponentFactory
     * @param supportRestore
     * @param tag
     * @param immediate
     * @return SceneDelegate
     */
    @Deprecated
    public static SceneDelegate setupWithFragment(final Fraction fragment,
                                                  int containerId,
                                                  Intent intent,
                                                  NavigationSceneOptions navigationSceneOptions,
                                                  SceneComponentFactory rootSceneComponentFactory,
                                                  final boolean supportRestore,
                                                  final String tag,
                                                  final boolean immediate) {
        return setupWithFragment(fragment, containerId, navigationSceneOptions, rootSceneComponentFactory, supportRestore, tag, immediate);
    }


    /**
     * setupWithFragment
     *
     * @param fragment
     * @param containerId
     * @param navigationSceneOptions
     * @param rootSceneComponentFactory
     * @param supportRestore
     * @param tag
     * @param immediate
     * @return SceneDelegate
     */
    private static SceneDelegate setupWithFragment(final Fraction fragment,
                                                   int containerId,
                                                   NavigationSceneOptions navigationSceneOptions,
                                                   SceneComponentFactory rootSceneComponentFactory,
                                                   final boolean supportRestore,
                                                   final String tag,
                                                   final boolean immediate) {
        ThreadUtility.checkUIThread();
        if (tag == null) {
            throw new IllegalArgumentException("tag cant be null");
        }
        checkDuplicateTag(fragment, tag);

        FractionManager manager = fragment.getFractionAbility().getFractionManager();
        Optional<Fraction> optionalFraction = fragment.getFractionAbility().getFractionManager().getFractionByTag(tag);
        LifeCycleCompatFragment lifeCycleFragment = null;
        if (optionalFraction.isPresent()) {
            lifeCycleFragment = (LifeCycleCompatFragment) optionalFraction.get();
        }

        if (lifeCycleFragment != null && !supportRestore) {
            FractionScheduler transaction = manager.startFractionScheduler();
            transaction.remove(lifeCycleFragment);
            FragmentUtility.commitFragment(transaction, immediate);
            lifeCycleFragment = null;
        }

        ViewFinder viewFinder = new FragmentViewFinder(fragment);
        final NavigationScene navigationScene = (NavigationScene) SceneInstanceUtility.getInstanceFromClass(NavigationScene.class,
                navigationSceneOptions.toBundle());
        navigationScene.setRootSceneComponentFactory(rootSceneComponentFactory);
        ScopeHolderCompatFragment targetScopeHolderFragment = null;
        SceneLifecycleDispatcher<NavigationScene> dispatcher = null;

        if (lifeCycleFragment != null) {
            final ScopeHolderCompatFragment scopeHolderFragment = ScopeHolderCompatFragment.install(fragment, tag, false, immediate);
            targetScopeHolderFragment = scopeHolderFragment;
            dispatcher = new SceneLifecycleDispatcher<>(containerId, viewFinder, navigationScene, scopeHolderFragment, supportRestore);
            lifeCycleFragment.setSceneContainerLifecycleCallback(dispatcher);
        } else {
            final ScopeHolderCompatFragment scopeHolderFragment = ScopeHolderCompatFragment.install(fragment, tag, !supportRestore, immediate);
            lifeCycleFragment = LifeCycleCompatFragment.newInstance(supportRestore);

            dispatcher = new SceneLifecycleDispatcher<>(containerId, viewFinder, navigationScene, scopeHolderFragment, supportRestore);
            lifeCycleFragment.setSceneContainerLifecycleCallback(dispatcher);
            FractionScheduler transaction = manager.startFractionScheduler();
            transaction.add(containerId, lifeCycleFragment, tag);
            targetScopeHolderFragment = scopeHolderFragment;
        }
        final LifeCycleCompatFragment finalLifeCycleFragment = lifeCycleFragment;
        final ScopeHolderCompatFragment finalTargetScopeHolderFragment = targetScopeHolderFragment;
        final SceneDelegate proxy = new SceneDelegate() {
            private boolean mAbandoned = false;

            @Override
            public boolean onBackPressed() {
                return !mAbandoned && navigationScene.onBackPressed();
            }

            @Override
            public NavigationScene getNavigationScene() {
                if (this.mAbandoned) {
                    return null;
                }
                return navigationScene;
            }

            @Override
            public void setNavigationSceneAvailableCallback(NavigationSceneAvailableCallback callback) {
                callback.onNavigationSceneAvailable(navigationScene);
            }

            @Override
            public void abandon() {
                if (this.mAbandoned) {
                    return;
                }
                this.mAbandoned = true;
                final Component view = navigationScene.getView();


                FractionScheduler transaction = manager.startFractionScheduler().remove(finalLifeCycleFragment).remove(finalTargetScopeHolderFragment);
                if (immediate) {
                    FragmentUtility.commitFragment(transaction, true);
                } else {
                    FragmentUtility.commitFragment(transaction, false);
                    removeTag(fragment, tag);
                    if (view != null) {
                        Utility.removeFromParentView(view);
                    }
                }
            }
        };
        return proxy;
    }

    /**
     * checkDuplicateTag
     *
     * @param fragment
     * @param tag
     */
    static void checkDuplicateTag(Fraction fragment, String tag) {
        if (CHECK_DUPLICATE_TAG_MAP.get(fragment) != null && CHECK_DUPLICATE_TAG_MAP.get(fragment).contains(tag)) {
            throw new IllegalArgumentException("tag duplicate, use another tag when invoke setupWithActivity for the second time in same Fraction");
        } else {
            HashSet<String> set = CHECK_DUPLICATE_TAG_MAP.get(fragment);
            if (set == null) {
                set = new HashSet<>();
                CHECK_DUPLICATE_TAG_MAP.put(fragment, set);
            }
            set.add(tag);
        }
    }

    /**
     * removeTag
     *
     * @param fragment
     * @param tag
     */
    static void removeTag(Fraction fragment, String tag) {
        CHECK_DUPLICATE_TAG_MAP.get(fragment).remove(tag);
    }
}
